/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */

/**
 * Specifies an environment on Fluid property of a IFluidPackage.
 * @legacy @beta
 */
export interface IFluidPackageEnvironment {
	/**
	 * The name of the target. For a browser environment, this could be umd for scripts
	 * or css for styles.
	 */
	[target: string]:
		| undefined
		| {
				/**
				 * List of files for the target. These can be relative or absolute.
				 * The code loader should resolve relative paths, and validate all
				 * full urls.
				 */
				files: string[];

				/**
				 * General access for extended fields as specific usages will
				 * likely have additional infornamation like a definition
				 * of Library, the entrypoint for umd packages.
				 */
				[key: string]: unknown;
		  };
}

/**
 * Fluid-specific properties expected on a package to be loaded by the code loader.
 * While compatible with the npm package format it is not necessary that that package is an
 * npm package:
 * {@link https://stackoverflow.com/questions/10065564/add-custom-metadata-or-config-to-package-json-is-it-valid}
 * @legacy @beta
 */
export interface IFluidPackage {
	/**
	 * The name of the package that this code represnets
	 */
	name: string;
	/**
	 * This object represents the Fluid specific properties of the package
	 */
	fluid: {
		/**
		 * The name of the of the environment. This should be something like browser, or node
		 * and contain the necessary targets for loading this code in that environment.
		 */
		[environment: string]: undefined | IFluidPackageEnvironment;
	};
	/**
	 * General access for extended fields as specific usages will
	 * likely have additional infornamation like a definition of
	 * compatible versions, or deployment information like rings or rollouts.
	 */
	[key: string]: unknown;
}

/**
 * Check if the package.json defines a Fluid package
 * @param pkg - the package json data to check if it is a Fluid package.
 * @legacy @beta
 */
export const isFluidPackage = (pkg: unknown): pkg is Readonly<IFluidPackage> =>
	typeof pkg === "object" &&
	typeof (pkg as Partial<IFluidPackage>)?.name === "string" &&
	typeof (pkg as Partial<IFluidPackage>)?.fluid === "object";

/**
 * A key-value mapping of config values.
 *
 * @remarks
 * Can be used by consumers for things like providing additional configuration for their package manager.
 *
 * @see {@link IFluidCodeDetails.config}
 *
 * @legacy @beta
 */
export interface IFluidCodeDetailsConfig {
	readonly [key: string]: string;
}

/**
 * Data structure used to describe the code to load on the Fluid document
 * @legacy @beta
 */
export interface IFluidCodeDetails {
	/**
	 * The code package to be used on the Fluid document.
	 * This is either the package name which will be loaded from a package manager, or the expanded Fluid package.
	 */
	readonly package: string | Readonly<IFluidPackage>;

	/**
	 * Configuration details and additional information about the Fluid document.
	 *
	 * @remarks
	 * This is mainly consumer-defined data (e.g. links to a package manager and base CDNs).
	 * Its contents and semantics (including whether or not this data is present) are completely up to the consumer,
	 * _except_ for keys starting with "FluidFramework." (e.g. "FluidFramework.CustomSetting1")
	 * which are reserved by the framework and should not be used by consumers.
	 * Outside of those reserved keys, Fluid does not interpret this data in any way.
	 */
	readonly config?: IFluidCodeDetailsConfig;
}

/**
 * Determines if any object is an IFluidCodeDetails
 * @internal
 */
export const isFluidCodeDetails = (
	details: unknown,
): details is Readonly<IFluidCodeDetails> => {
	const maybeCodeDetails = details as Partial<IFluidCodeDetails> | undefined;
	return (
		typeof maybeCodeDetails === "object" &&
		(typeof maybeCodeDetails?.package === "string" ||
			isFluidPackage(maybeCodeDetails?.package)) &&
		(maybeCodeDetails?.config === undefined || typeof maybeCodeDetails?.config === "object")
	);
};

/**
 * @legacy @beta
 */
export const IFluidCodeDetailsComparer: keyof IProvideFluidCodeDetailsComparer =
	"IFluidCodeDetailsComparer";

/**
 * @legacy @beta
 */
export interface IProvideFluidCodeDetailsComparer {
	readonly IFluidCodeDetailsComparer: IFluidCodeDetailsComparer;
}

/**
 * Provides capability to compare Fluid code details.
 * @legacy @beta
 */
export interface IFluidCodeDetailsComparer extends IProvideFluidCodeDetailsComparer {
	/**
	 * Determines if the `candidate` code details satisfy the constraints specified in `constraint` code details.
	 *
	 * Similar semantics to:
	 * {@link https://github.com/npm/node-semver#usage}
	 */
	satisfies(candidate: IFluidCodeDetails, constraint: IFluidCodeDetails): Promise<boolean>;

	/**
	 * Return a number representing the ascending sort order of the `a` and `b` code details:
	 *
	 * - `< 0` if `a < b`.
	 *
	 * - `= 0` if `a === b`.
	 *
	 * - `> 0` if `a > b`.
	 *
	 * - `undefined` if `a` is not comparable to `b`.
	 *
	 * Similar semantics to:
	 * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#description | Array.sort}
	 */
	compare(a: IFluidCodeDetails, b: IFluidCodeDetails): Promise<number | undefined>;
}
